#include <algorithm/analytical_model/algorithm/cuda/GeometryUtils.cuh>
#include <algorithm/analytical_model/algorithm/cuda/VectorMath.cuh>
#include <algorithm/analytical_model/algorithm/cuda/receiver/RectangleReceiver.cuh>

#include <algorithm>
#include <iterator>

using namespace solar::cuda;

RectangleReceiver::RectangleReceiver(cudaStream_t stream) : Receiver(stream) {}

__device__ __host__
RectangleReceiver::RectangleReceiver(const RectangleReceiver& rectangle_receiver)
    : Receiver(rectangle_receiver), local_normal_(rectangle_receiver.local_normal_)
{
#ifdef __CUDA_ARCH__
    for (int i = 0; i < std::size(rectangle_receiver.rect_vertices_); i++)
    {
        this->rect_vertices_[i].x = rectangle_receiver.rect_vertices_[i].x;
        this->rect_vertices_[i].y = rectangle_receiver.rect_vertices_[i].y;
        this->rect_vertices_[i].z = rectangle_receiver.rect_vertices_[i].z;
    }
#else
    std::copy(std::begin(rectangle_receiver.rect_vertices_),
              std::end(rectangle_receiver.rect_vertices_), std::begin(rect_vertices_));
#endif
}

void RectangleReceiver::init(float pixel_per_meter_for_receiver)
{
    pixel_length_ = 1.0F / float(pixel_per_meter_for_receiver);
    initVertex();
    setFocusCenter();
    setResolution(pixel_per_meter_for_receiver);
    allocImage(); // cudaMalloc according to resolution. size = resulotion.x * resolution.y
    cleanImageContent();
}

void RectangleReceiver::setFocusCenter()
{
    focus_center_ = (rect_vertices_[0] + rect_vertices_[2]) / 2;
}

void RectangleReceiver::initVertex()
{
    setLocalNormal(); // Set local normal
    setLocalVertex(); // Set local vertex according to face type
    setWorldVertex(); // Set world vertex according to normal
}

void RectangleReceiver::setLocalNormal()
{
    switch (surface_index_)
    {
    case 0:
        local_normal_ = ::make_float3(0.0F, 0.0F, 1.0F);
        break;
    case 1:
        local_normal_ = ::make_float3(1.0F, 0.0F, 0.0F);
        break;
    case 2:
        local_normal_ = ::make_float3(0.0F, 0.0F, -1.0F);
        break;
    case 3:
        local_normal_ = ::make_float3(-1.0F, 0.0F, 0.0F);
        break;
    case 4:
        local_normal_ = ::make_float3(0.0F, 1.0F, 0.0F);
        break;
    case 5:
        local_normal_ = ::make_float3(0.0F, -1.0F, 0.0F);
        break;
    default:
        break;
    }
}

void RectangleReceiver::setLocalVertex()
{
    switch (surface_index_)
    {
    case 0:
        rect_vertices_[0] = ::make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertices_[1] = ::make_float3(-size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertices_[2] = ::make_float3(size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertices_[3] = ::make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        break;
    case 1:
        rect_vertices_[0] = ::make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertices_[1] = ::make_float3(size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertices_[2] = ::make_float3(size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertices_[3] = ::make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    case 2:
        rect_vertices_[0] = ::make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertices_[1] = ::make_float3(size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertices_[2] = ::make_float3(-size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertices_[3] = ::make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    case 3:
        rect_vertices_[0] = ::make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertices_[1] = ::make_float3(-size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertices_[2] = ::make_float3(-size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertices_[3] = ::make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        break;
    case 5:
        rect_vertices_[0] = ::make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertices_[1] = ::make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertices_[2] = ::make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertices_[3] = ::make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    default:
        break;
    }
}

void RectangleReceiver::setWorldVertex()
{
    normal_ = normalize(normal_);
    for (auto& vertex : rect_vertices_)
    {
        vertex = rotateY(vertex, local_normal_, normal_);
    }
    for (auto& vertex : rect_vertices_)
    {
        vertex = translate(vertex, pos_);
    }

    // std::printf("%f %f %f\n", rect_vertices_[0].x, rect_vertices_[0].y, rect_vertices_[0].z);
    // std::printf("%f %f %f\n", rect_vertices_[1].x, rect_vertices_[1].y, rect_vertices_[1].z);
    // std::printf("%f %f %f\n", rect_vertices_[2].x, rect_vertices_[2].y, rect_vertices_[2].z);
    // std::printf("%f %f %f\n", rect_vertices_[3].x, rect_vertices_[3].y, rect_vertices_[3].z);
}

void RectangleReceiver::setResolution(float pixel_per_meter_for_receiver)
{
    switch (surface_index_)
    {
    case 0:
        resolution_.x = size_.x * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.y * float(pixel_per_meter_for_receiver);
        break;
    case 1:
        resolution_.x = size_.z * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.y * float(pixel_per_meter_for_receiver);
        // std::printf("       resolution %d %d \n", resolution_.x, resolution_.y);
        break;
    case 2:
        resolution_.x = size_.x;
        resolution_.y = size_.y;
        break;
    case 3:
        resolution_.x = size_.z;
        resolution_.y = size_.y;
        break;
    default:
        break;
    }
    // std::printf("size= %f %f %f\n", size_.x, size_.y, size_.z);
    // std::printf("surface_index %d resolution %d %d \n", surface_index_, resolution_.x,
    // resolution_.y); 
    // resolution_.x*=float(pixel_per_meter_for_receiver); 
    // resolution_.y*=float(pixel_per_meter_for_receiver);
    n_resolution_ = resolution_.x * resolution_.y;
}

__host__ __device__ auto RectangleReceiver::getFocusCenter(const float3& /*heliostat_position*/) -> float3
{
    return focus_center_;
}

__device__ __host__ auto RectangleReceiver::intersect(const float3& orig, const float3& dir)
    -> std::tuple<float, float, float, bool>
{
    return rayParallelogramIntersect(orig, dir, rect_vertices_[0], rect_vertices_[1],
                                     rect_vertices_[3]);
}
